//
// TriangleSelection.java
//
package com.photoexpert;

// TriangleSelection represents a triangle selection area

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;


class TriangleSelection {

    TriangleSelection() {
        _presentor = Presentor.Instance();
        _zoomRate = _presentor.getZoomRate();
        // create Thread that draws the selection border
        final Runnable redrawer = new Runnable() {
            @Override
            public void run() {
                _presentor.repaint(0, _minX, _minY, _width, _height);
            }
        };
        _drawer = new Runnable() {
            @Override
            public void run() {
                while(_width > 0 && _height > 0) {
                    try {
                        if(++_drawStart >= _drawStep * 2)
                            _drawStart = 0;
                        EventQueue.invokeLater(redrawer);
                        Thread.sleep(100);
                    }
                    catch (InterruptedException e) {
                        break;
                    }
                }
                _thread = null;
            }
        };
    }

    TPoint getPoint1() { return _p1; }
    TPoint getPoint2() { return _p2; }
    TPoint getPoint3() { return _p3; }
    void setPoint1(int x, int y) { setPoint(_p1, x, y); }
    void setPoint2(int x, int y) { setPoint(_p2, x, y); }
    void setPoint3(int x, int y) { setPoint(_p3, x, y); }

    void destroy() {
        _logger.log(Level.INFO, "Selection destroyed");
        _p1._x = 0;
        _p1._y = 0;
        _p2._x = 0;
        _p2._y = 0;
        _p3._x = 0;
        _p3._y = 0;
        int minX = _minX;
        int minY = _minY;
        int width = _width;
        int height = _height;
        _minX = 0;
        _minY = 0;
        _width = 0;
        _height = 0;
        _maxX = 0;
        _maxY = 0;
        _presentor.repaint(0, minX, minY, width, height);
        App.Instance().selectionBecomeValid(false);
    }

    void zoomRateChanged() {
        double zoomRate = _presentor.getZoomRate();
        _p1._x = (int)(_p1._x / _zoomRate * zoomRate);
        _p1._y = (int)(_p1._y / _zoomRate * zoomRate);
        _p2._x = (int)(_p2._x / _zoomRate * zoomRate);
        _p2._y = (int)(_p2._y / _zoomRate * zoomRate);
        _p3._x = (int)(_p3._x / _zoomRate * zoomRate);
        _p3._y = (int)(_p3._y / _zoomRate * zoomRate);
        _minX = (int)(_minX / _zoomRate * zoomRate);
        _minY = (int)(_minY / _zoomRate * zoomRate);
        _maxX = (int)(_maxX / _zoomRate * zoomRate);
        _maxY = (int)(_maxY / _zoomRate * zoomRate);
        _width = (int)(_width / _zoomRate * zoomRate);
        _height = (int)(_height / _zoomRate * zoomRate);
        _zoomRate = zoomRate;
    }

    void startDrawBorder() {
        if(_thread == null) {
            _thread = new Thread(_drawer);
            _thread.start();
        }
    }

    void drawBorder(Graphics g) {
        drawLine(g, _p1._x, _p1._y, _p2._x,_p2._y);
        drawLine(g, _p1._x, _p1._y, _p3._x,_p3._y);
        drawLine(g, _p3._x, _p3._y, _p2._x,_p2._y);
    }

    boolean copy() {
        TPoint p[] = new TPoint[3];
        p[0] = new TPoint((int)(_p1._x / _zoomRate), (int)(_p1._y / _zoomRate));
        p[1] = new TPoint((int)(_p2._x / _zoomRate), (int)(_p2._y / _zoomRate));
        p[2] = new TPoint((int)(_p3._x / _zoomRate), (int)(_p3._y / _zoomRate));
        _logger.log(Level.INFO, "Copy triangle selection to clipboard:{0},{1},{2}", p);
        int minX = Math.min(p[0]._x, Math.min(p[1]._x, p[2]._x));
        int minY = Math.min(p[0]._y, Math.min(p[1]._y, p[2]._y));
        int width = Math.max(p[0]._x, Math.max(p[1]._x, p[2]._x))  - minX + 1;
        int height = Math.max(p[0]._y, Math.max(p[1]._y, p[2]._y)) - minY + 1;
        p[0]._x -= minX;
        p[0]._y -= minY;
        p[1]._x -= minX;
        p[1]._y -= minY;
        p[2]._x -= minX;
        p[2]._y -= minY;
        RangeDecider rd = new RangeDecider(p, width - 1, height - 1);
        if(rd._type == 0) {
            JOptionPane.showMessageDialog(App.Instance(), "Unable to determine type.", "Photo Expert",
                JOptionPane.ERROR_MESSAGE);
        }
        _logger.log(Level.INFO, "Triangle type:{0}", rd._type);
        BufferedImage src = Implementor.Instance().getImage().getSubimage(minX, minY, width, height);
        BufferedImage dst = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        // fill transparency first
        float[] scales = new float[] { 1f, 1f, 1f, 0f };
        float[] offsets = new float[] { 0f, 0f, 0f, 0f };
        RescaleOp operator = new RescaleOp(scales, offsets, null);
        operator.filter(dst, dst);
        for(int x = 0; x < width; ++x)
            for(int y = 0; y < height; ++y)
                if(rd.isIn(x, y)) {
                    int rgb = src.getRGB(x, y);
                    dst.setRGB(x, y, rgb);
                }
        ImageTransferable imageSelection = new ImageTransferable(dst);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imageSelection, null);
        return true;
    }

    void delete() {
        TPoint p[] = new TPoint[3];
        p[0] = new TPoint((int)(_p1._x / _zoomRate), (int)(_p1._y / _zoomRate));
        p[1] = new TPoint((int)(_p2._x / _zoomRate), (int)(_p2._y / _zoomRate));
        p[2] = new TPoint((int)(_p3._x / _zoomRate), (int)(_p3._y / _zoomRate));
        _logger.log(Level.INFO, "Delete triangle selection:{0},{1},{2}", p);
        int minX = Math.min(p[0]._x, Math.min(p[1]._x, p[2]._x));
        int minY = Math.min(p[0]._y, Math.min(p[1]._y, p[2]._y));
        int width = Math.max(p[0]._x, Math.max(p[1]._x, p[2]._x))  - minX + 1;
        int height = Math.max(p[0]._y, Math.max(p[1]._y, p[2]._y)) - minY + 1;
        p[0]._x -= minX;
        p[0]._y -= minY;
        p[1]._x -= minX;
        p[1]._y -= minY;
        p[2]._x -= minX;
        p[2]._y -= minY;
        RangeDecider rd = new RangeDecider(p, width - 1, height - 1);
        if(rd._type == 0) {
            JOptionPane.showMessageDialog(App.Instance(), "Unable to determine type.", "Photo Expert",
                JOptionPane.ERROR_MESSAGE);
        }
        _logger.log(Level.INFO, "Triangle type:{0}", rd._type);
        BufferedImage img = Implementor.Instance().getImage().getSubimage(minX, minY, width, height);
        int rgb = img.getTransparency() == Transparency.OPAQUE ? 0xffffff : 0x00000000;
        for(int x = 0; x < width; ++x)
            for(int y = 0; y < height; ++y)
                if(rd.isIn(x, y)) {
                    img.setRGB(x, y, rgb);
                }
    }

    /* ----------------------------------------------------------------------------------------- */

    private void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
        if(Math.abs(x2 - x1) >= Math.abs(y2 - y1)) {
            if(x2 < x1) {
                // switch point1 and point2 so always start from the smaller
                int t = x2;
                x2 = x1;
                x1 = t;
                t = y2;
                y2 = y1;
                y1 = t;
            }
            g.setColor(Color.BLACK);
            double ratio = (y2 - y1) * 1d / (x2 - x1);
            int tx1 = x1 + _drawStart;
            int ty1 = (int)(y1 + ratio * (tx1 - x1));
            if(_drawStart > _drawStep) {
                int tx2 = tx1 - _drawStep - 1;
                int ty2 = (int)(y1 + ratio * (tx2 - x1));
                g.drawLine(x1, y1, tx2, ty2);
            }
            int tx2 = tx1 + _drawStep - 1;
            int ty2 = (int)(y1 + ratio * (tx2 - x1));
            while(tx2 <= x2) {
                g.drawLine(tx1, ty1, tx2, ty2);
                tx1 += _drawStep * 2;
                tx2 = tx1 + _drawStep - 1;
                ty1 = (int)(y1 + ratio * (tx1 - x1));
                ty2 = (int)(y1 + ratio * (tx2 - x1));
            }
            if(tx1 < x2)
                g.drawLine(tx1, ty1, x2, y2);
            g.setColor(Color.WHITE);
            tx1 = x1 + _drawStart + _drawStep;
            ty1 = (int)(y1 + ratio * (tx1 - x1));
            if(_drawStart < _drawStep) {
                tx2 = tx1 - _drawStep - 1;
                ty2 = (int)(y1 + ratio * (tx2 - x1));
                g.drawLine(x1, y1, tx2, ty2);
            } else {
                tx1 -= _drawStep * 2;
                ty1 = (int)(y1 + ratio * (tx1 - x1));
            }
            tx2 = tx1 + _drawStep - 1;
            ty2 = (int)(y1 + ratio * (tx2 - x1));
            while(tx2 <= x2) {
                g.drawLine(tx1, ty1, tx2, ty2);
                tx1 += _drawStep * 2;
                tx2 = tx1 + _drawStep - 1;
                ty1 = (int)(y1 + ratio * (tx1 - x1));
                ty2 = (int)(y1 + ratio * (tx2 - x1));
            }
            if(tx1 < x2)
                g.drawLine(tx1, ty1, x2, y2);
        } else {
            if(y2 < y1) {
                // switch point1 and point2 so always start from the smaller
                int t = x2;
                x2 = x1;
                x1 = t;
                t = y2;
                y2 = y1;
                y1 = t;
            }
            g.setColor(Color.BLACK);
            double ratio = (x2 - x1) * 1d / (y2 - y1);
            int ty1 = y1 + _drawStart;
            int tx1 = (int)(x1 + ratio * (ty1 - y1));
            if(_drawStart > _drawStep) {
                int ty2 = ty1 - _drawStep - 1;
                int tx2 = (int)(x1 + ratio * (ty2 - y1));
                g.drawLine(x1, y1, tx2, ty2);
            }
            int ty2 = ty1 + _drawStep - 1;
            int tx2 = (int)(x1 + ratio * (ty2 - y1));
            while(ty2 <= y2) {
                g.drawLine(tx1, ty1, tx2, ty2);
                ty1 += _drawStep * 2;
                ty2 = ty1 + _drawStep - 1;
                tx1 = (int)(x1 + ratio * (ty1 - y1));
                tx2 = (int)(x1 + ratio * (ty2 - y1));
            }
            if(ty1 < y2)
                g.drawLine(tx1, ty1, x2, y2);
            g.setColor(Color.WHITE);
            ty1 = y1 + _drawStart + _drawStep;
            tx1 = (int)(x1 + ratio * (ty1 - y1));
            if(_drawStart < _drawStep) {
                ty2 = ty1 - _drawStep - 1;
                tx2 = (int)(x1 + ratio * (ty2 - y1));
                g.drawLine(x1, y1, tx2, ty2);
            } else {
                ty1 -= _drawStep * 2;
                tx1 = (int)(x1 + ratio * (ty1 - y1));
            }
            ty2 = ty1 + _drawStep - 1;
            tx2 = (int)(x1 + ratio * (ty2 - y1));
            while(ty2 <= y2) {
                g.drawLine(tx1, ty1, tx2, ty2);
                ty1 += _drawStep * 2;
                ty2 = ty1 + _drawStep - 1;
                tx1 = (int)(x1 + ratio * (ty1 - y1));
                tx2 = (int)(x1 + ratio * (ty2 - y1));
            }
            if(ty1 < y2)
                g.drawLine(tx1, ty1, x2, y2);
        }
    }

    private void setPoint(TPoint p, int x, int y) {
        // trace the old point -- if the area is shrinking, need to repaint old area
        int x0 = p._x;
        int y0 = p._y;
        p._x = x;
        p._y = y;
        _minX = Math.min(_p1._x, Math.min(_p2._x, _p3._x));
        _maxX = Math.max(_p1._x, Math.max(_p2._x, _p3._x));
        _minY = Math.min(_p1._y, Math.min(_p2._y, _p3._y));
        _maxY = Math.max(_p1._y, Math.max(_p2._y, _p3._y));
        _width = _maxX - _minX + 1;
        _height = _maxY - _minY + 1;
        int minX = Math.min(x0, _minX);
        int minY = Math.min(y0, _minY);
        int maxX = Math.max(x0, _maxX);
        int maxY = Math.max(y0, _maxY);
        _presentor.repaint(0, minX, minY, maxX - minX + 1, maxY - minY + 1);
    }

    public static class TPoint {
        TPoint() {}
        TPoint(int x, int y) { _x = x; _y = y; }
        boolean lessThan(TPoint that) {
            return _x < that._x || (_x == that._x && _y < that._y);
        }
        @Override
        public String toString() {
            return new Integer(_x).toString() + "-" + new Integer(_y).toString();
        }
        int     _x;
        int     _y;
    }

    private TPoint      _p1 = new TPoint();
    private TPoint      _p2 = new TPoint();
    private TPoint      _p3 = new TPoint();
    private int         _minX;
    private int         _minY;
    private int         _maxX;
    private int         _maxY;
    private int         _width;
    private int         _height;

    private final class RangeDecider {
        RangeDecider(TPoint[] p, int width, int height) {
            _p = p;
            _width = width;
            _height = height;
            sort();
            _type = getType();
        }
        // sort by x first, then y
        void sort() {
            if(_p[1].lessThan(_p[0])) {
                TPoint t = _p[0];
                _p[0] = _p[1];
                _p[1] = t;
            }
            if(_p[2].lessThan(_p[0])) {
                TPoint t = _p[2];
                _p[2] = _p[1];
                _p[1] = _p[0];
                _p[0] = t;
            } else if(_p[2].lessThan(_p[1])) {
                TPoint t = _p[2];
                _p[2] = _p[1];
                _p[1] = t;
            }
        }
        // triangles can be categorized to 20 types by x and y
        int getType() {
            if(_p[0]._x == 0)
                if(_p[0]._y == 0)
                    if(_p[1]._x == 0)
                        if(_p[1]._y < _height)
                            return 1;
                        else // p[1]._y == height
                            if(_p[2]._y == 0)
                                return 2;
                            else if(_p[2]._y < _height)
                                return 3;
                            else // p[2]._y == height
                                return 4;
                    else if(_p[1]._x < _width)
                        if(_p[1]._y == 0)
                            return 5;
                        else // p[1]._y == height
                            if(_p[2]._y == 0)
                                return 6;
                            else if(_p[2]._y < _height)
                                return 7;
                            else // p[2]._y == height
                                return 8;
                    else // p[1]._x == width
                        if(_p[1]._y == 0)
                            return 9;
                        else // p[1]._y > 0
                            return 10;
                else if(_p[0]._y < _height)
                    if(_p[1]._x == 0)
                        return 11;
                    else if(_p[1]._x < _width)
                        if(_p[1]._y == 0)
                            return 12;
                        else // p[1]._y == height
                            return 13;
                    else // p[1]._x == width
                        return 14;
                else // p[0]._y == height
                    if(_p[1]._x < _width)
                        if(_p[1]._y == 0)
                            if(_p[2]._y == 0)
                                return 15;
                            else if(_p[2]._y < _height)
                                return 16;
                            else // p[2]._y == height
                                return 17;
                        else // p[1]._y == height
                            return 18;
                    else // p[1]._x == width
                        if(_p[2]._y < _height)
                            return 19;
                        else
                            return 20;
            return 0;
        }
        boolean isIn(int x, int y) {
            switch(_type) {
                case 1 :
                    double _r1_1 = _height * 1d / _width;
                    if(y < x * _r1_1)
                        return false;
                    double _r1_2 = (_height - _p[1]._y) * 1d / _width;
                    if(y > _p[1]._y + x * _r1_2)
                        return false;
                    return true;
                case 2 :
                    double _r2_1 = _height * -1d / _width;
                    if(y > x * _r2_1 + _height)
                        return false;
                    return true;
                case 3 :
                    double _r3_1 = _p[2]._y * 1d / _width;
                    if(y < _r3_1 * x)
                        return false;
                    double _r3_2 = (_height - _p[2]._y) * -1d / _width;
                    if(y > _height + x * _r3_2)
                        return false;
                    return true;
                case 4 :
                    double _r4_1 = _height * 1d / _width;
                    if(y < x * _r4_1)
                        return false;
                    return true;
                case 5 :
                    double _r5_1 = _height * 1d / _width;
                    if(y > x * _r5_1)
                        return false;
                    double _r5_2 = _height * 1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y < _r5_2 * (x - _p[1]._x))
                        return false;
                    return true;
                case 6 :
                    double _r6_1 = _height * 1d / _p[1]._x;
                    if(x < _p[1]._x && y > x * _r6_1)
                        return false;
                    double _r6_2 = _height * -1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y > _height + _r6_2 * (x - _p[1]._x))
                        return false;
                    return true;
                case 7 :
                    double _r7_1 = _p[2]._y * 1d/ _width;
                    if(y < x * _r7_1)
                        return false;
                    double _r7_2 = _height * 1d / _p[1]._x;
                    if(x < _p[1]._x && y > _r7_2 * x)
                        return false;
                    double _r7_3 = (_height - _p[2]._y) * -1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y > _height + _r7_3 * (x - _p[1]._x))
                        return false;
                    return true;
                case 8 :
                    double _r8_1 = _height * 1d / _width;
                    if(y < _r8_1 * x)
                        return false;
                    double _r8_2 = _height * 1d / _p[1]._x;
                    if(x < _p[1]._x && y > _r8_2 * x)
                        return false;
                    return true;
                case 9 :
                    double _r9_1 = _height * 1d / _width;
                    if(y > _r9_1 * x)
                        return false;
                    return true;
                case 10 :
                    double _r10_1 = _height * 1d / _width;
                    if(y > _r10_1 * x)
                        return false;
                    double _r10_2 = _p[1]._y * 1d / _width;
                    if(y < x * _r10_2)
                        return false;
                    return true;
                case 11 :
                    double _r11_1 = _p[0]._y * -1d / _width;
                    if(y < x * _r11_1 + _p[0]._y)
                        return false;
                    double _r11_2 = _height * -1d / _width;
                    if(y > _r11_2 * x + _height)
                        return false;
                    return true;
                case 12 :
                    double _r12_1 = _p[0]._y * -1d / _p[1]._x;
                    if(x < _p[1]._x && y < _r12_1 * x + _p[0]._y)
                        return false;
                    double _r12_2 = (_height - _p[0]._y) * 1d / _width;
                    if(y > _p[0]._y + _r12_2 * x)
                        return false;
                    double _r12_3 = _height * 1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y < _r12_3 * (x - _p[1]._x))
                        return false;
                    return true;
                case 13 :
                    double _r13_1 = _p[0]._y * -1d / _width;
                    if(y < _r13_1 * x + _p[0]._y)
                        return false;
                    double _r13_2 = (_p[1]._y - _p[0]._y) * 1d / (_p[1]._x - _p[0]._x);
                    if(x < _p[1]._x && y > _p[0]._y + _r13_2 * x)
                        return false;
                    double _r13_3 = _height * -1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y > _r13_3 * (x - _p[1]._x) + _height)
                        return false;;
                    return true;
                case 14 :
                    double _r14_1 = _p[0]._y * -1d / _width;
                    if(y < _r14_1 * x + _p[0]._y)
                        return false;
                    double _r14_2 = (_height - _p[0]._y) * 1d / _width;
                    if(y > _p[0]._y + _r14_2 * x)
                        return false;
                    return true;
                case 15 :
                    double _r15_1 = _height * -1d / _p[1]._x;
                    if(x < _p[1]._x && y < _height + _r15_1 * x)
                        return false;
                    double _r15_2 = _height * -1d / _width;
                    if(y > _height + _r15_2 * x)
                        return false;
                    return true;
                case 16 :
                    double _r16_1 = _height * -1d / _p[1]._x;
                    if(x < _p[1]._x && y < _height + _r16_1 * x)
                        return false;
                    double _r16_2 = (_height - _p[2]._y) * -1d / _width;
                    if(y > _height + _r16_2 * x)
                        return false;
                    double _r16_3 = _p[2]._y * 1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y < _r16_3 * (x - _p[1]._x))
                        return false;
                    return true;
                case 17 :
                    double _r17_1 = _height * -1d / _p[1]._x;
                    if(x < _p[1]._x && y < _r17_1 * x + _height)
                        return false;
                    double _r17_2 = _height * 1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y < _r17_2 * (x - _p[1]._x))
                        return false;
                    return true;
                case 18 :
                    double _r18_1 = _height * -1d / _width;
                    if(y < _height + _r18_1 * x)
                        return false;
                    double _r18_2 = _height * -1d / (_width - _p[1]._x);
                    if(x > _p[1]._x && y > _height + _r18_2 * (x - _p[1]._x))
                        return false;
                    return true;
                case 19 :
                    double _r19_1 = _height * -1d / _width;
                    if(y < _height + _r19_1 * x)
                        return false;
                    double _r19_2 = (_height - _p[2]._y) * -1d / _width;
                    if(y > _height + _r19_2 * x)
                        return false;
                    return true;
                case 20 :
                    double _r20_1 = _height * -1d / _width;
                    if(y < _height + _r20_1 * x)
                        return false;
                    return true;
            }
            return true;
        }
        final TPoint[]  _p;
        final int       _type;
        final int       _width;
        final int       _height;
    }

    private static final Logger _logger = App._logger;
    private Presentor           _presentor = null;
    private double              _zoomRate = 1d;
    private int                 _drawStart = 0;
    private final int           _drawStep = 5;
    private Thread              _thread = null;
    private Runnable            _drawer = null;
}
